
#include <string.h>

#include "contract.h"

#include "W25Q.h"
#include "BlockDevice.h"

#include "FreeRTOS.h"
#include "semphr.h"

static SemaphoreHandle_t bdLock;


static uint8_t bdStatus;
static uint8_t bdCachBuffer[BD_PageSize];
static uint32_t bdCachAddress;
static uint32_t bdCachLength;

void BD_Lock(void){
	ASSERT(bdLock);
//		xSemaphoreTake(bdLock, 1000 * portTICK_PERIOD_MS);
	xSemaphoreTake(bdLock, portMAX_DELAY);	
}

void BD_Unlock(void){
	ASSERT(bdLock);
	xSemaphoreGive(bdLock);
}

static int BD_IsInCach(uint32_t address){
	return ((bdCachAddress <= address) 
			&& (address < (bdCachAddress + bdCachLength)));
}

int BD_Read(uint32_t address, void* buffer, uint32_t length){
	if ( address + length > BD_Size){
		return -1;
	}
	W25Q_ReadMemory(address, length, (uint8_t*)buffer);
	return 0;
}

int BD_SafeRead(uint32_t address, void* buffer, uint32_t length){
	BD_Lock();
	int ret = BD_Read(address, buffer, length);
	BD_Unlock();
	return ret;
}

int BD_Cach(uint32_t address){
	if(address >= BD_Size){
		return -1;
	}
	uint32_t pageAddr = address & ~(BD_PageSize - 1);

	if(pageAddr != bdCachAddress || bdCachLength != sizeof(bdCachBuffer)){
		bdCachLength = sizeof(bdCachBuffer);
		bdCachAddress = pageAddr;
		BD_Read(pageAddr, bdCachBuffer, sizeof(bdCachBuffer));
	}
	return 0;

}

int BD_CachRead(uint32_t address, void* buffer, uint32_t length){
	uint8_t* data = buffer;
	uint32_t addr = address;
	uint32_t len = length;
	if ( address + length > BD_Size){
		return -1;
	}
	while(len > 0){
		if((bdCachAddress <= addr) 
			&& (addr < (bdCachAddress + bdCachLength))){
			//缓冲命中
			uint32_t aimLength = bdCachAddress + bdCachLength - addr;
			uint32_t copyLength = len < aimLength ? len : aimLength;
			uint32_t off = addr - bdCachAddress;
			memcpy(data, bdCachBuffer + off, copyLength);
			len -= copyLength;
			addr += copyLength;
			data += copyLength;
			continue;
		}
		if(len > sizeof(bdCachBuffer)){
			//读取大于缓冲空间，越过，不缓冲
			BD_Read(addr, data, len);
			break;
		}
		//刷新缓冲
		BD_Cach(addr);
	}
	return 0;
}


int BD_Program(uint32_t address, void* buffer, uint32_t length){	
	if ( address + length > BD_Size){
		return -1;
	}
	if(BD_IsInCach(address)){
		bdCachLength = 0;
	}
	W25Q_WriteMemory(address, length, (uint8_t*)buffer);
	return 0;
}



int BD_Erase(uint32_t address){
	uint32_t sector = BD_ToSectorAddress(address);
	if(sector / BD_SectorSize >= BD_SectorNum){
		return -1;
	}
	if(BD_IsInCach(address)){
		bdCachLength = 0;
	}
	W25Q_ErasaMemory(sector, BD_SectorSize);
	return 0;
}

int BD_SafeWrite(uint32_t address, void* buffer, uint32_t length){
	BD_Lock();
	int ret = BD_Erase(address);
	if(!ret){
		ret = BD_Program(address, buffer, length);
	}
	BD_Unlock();
	return ret;
}

void BD_Init(void){
	unsigned int i;
	bdCachLength = 0;
	for(i = 0; i < 3; i++){
		bdStatus = W25Q_Present();
		if(bdStatus){
			break;
		}
	}
	bdLock = xSemaphoreCreateBinary();
	if(bdLock){
		BD_Unlock();
	}
}



